/*->c.vm     */

/* Ovation!   (c) D. J. Pilling,  December 1988                     */
/*                         Memory Manager                             */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>

#include "h.os"
#include "h.wimp"
#include "h.sprite"
#include "h.font"
#include "h.bbc"
#include "h.wimpt"
#include "h.werr"


#include "h.def"

#include "h.wos"
#include "h.file"
#include "h.ram"
#include "h.dc"
#include "h.sp"
#include "h.main"

#include "h.vm"



#ifdef DBUG

void dumpbuff(buffer * b,char * ss)
{
 int maxlen=b->maxlen;
 FILE * f=fopen(ss,"wb");
 fwrite(b,maxlen,1,f);
 fclose(f);
}


void dumpsrc(xstr * src)
{
 buffer * b=src->u.source.txanc;
 dumpbuff(b,"xx");
}



void dumpstats(char * mess)
{
 buffer * b;

 dprintf(0,"%s base=%d top=%d hibuff=%d \n",mess,base,top,hibuff);
 b=(buffer *)base;
 while(1)
 {
  dprintf(1,"b=%x len=%d goff=%d maxlen=%d previous=%d\n",(int)b,b->len,b->goffset,b->maxlen,b->previous);
  if(((int)b)==hibuff) break;

  b=(buffer*)(((int)b)+b->maxlen);
 }
}

#endif




#define BASEPAGE    0x8000
#define MINMEM      0x2000
#define SLOTSTEP    0x2000
#define MAXFREE     0x8000
#define BUFFSTEP    0x800


int   lomem;       /* this points to the top of the program        */
int   himem;       /* this points to the end of our memory         */
int   minslotsize; /* this is the initial slot size below which we do not go */


/*
   all pointers end on word boundaries
   we quantise the node and buffer space in 8K units 

 */



/*****************************************************************************/
                         /* OS memory control functions */

/* return available free memory */

/*

int freesize(void)
{
 int  free;
 int  current;
 int  next;

 current=-1;
 next=-1;

 wimp_slotsize(&current,&next,&free);

 return(free+next);
}

 */


void nomem(void)
{
 errorbox("Not enough memory");
}


/* return the current slot size */

int slotsize(void)
{
 int  free;
 int  current;
 int  next;

 current=-1;
 next=-1;

 wimp_slotsize(&current,&next,&free);

 return(current);
}


/* set the slot to the given size */

int changeslotsize(int size)
{
 int  free;
 int  current;
 int  next;

 current=size;
 next=-1;

 wimp_slotsize(&current,&next,&free);

 return(current);
}


/* increase the slot size by the given amount */

void sethimem(int newhimem)
{
 int size;
 size=newhimem-BASEPAGE;
 changeslotsize(size);
 himem=slotsize()+BASEPAGE;
}



/****************************************************************************/
                        /* Buffer Management */


int top;              /* end of buffer space                */
int base;             /* start of buffer space              */
int hibuff;           /* pointer to start of highest buffer */
int freebuff;         /* free memory in buffer space        */


void buffinit(void)
{
 top=himem;
 base=lomem;
 freebuff=top-base;
 hibuff=0;
}


/* changes the size of the gap in the buffer   */

void changesizegap(int newsize,int oldsize,buffer * b)
{
 char * from=((char*)b)+b->goffset+oldsize;
 char *   to=from+newsize-oldsize;
 int    size=b->len-b->goffset;
 memmove(to,from,size);
}


void extendbuffer(buffer * b,int newmax)
{
 int oldsize=b->maxlen-b->len;
 int newsize=newmax-b->len;
 changesizegap(newsize,oldsize,b);
 b->maxlen=newmax;

 if(((int)b)!=hibuff)            /* setting previous length on next buffer */
  {
   b=(buffer*)(((int)b)+newmax);
   b->previous=newmax;
  }

}


/* points the gap to a given offset in the buffer */

void setgapto(buffer * b,int offset)
{
 char * buff;
 int    len;
 int    maxlen;
 int    goffset;
 int    gapsize;

 buff=(char *)b;
 len=b->len;
 maxlen=b->maxlen;
 goffset=b->goffset;
 gapsize=maxlen-len;

 if(offset>goffset) offset+=gapsize;


 if(goffset>offset)
   {
    memmove(buff+offset+gapsize,buff+offset,goffset-offset);
    b->goffset=offset;
   }
 else
 if(offset>goffset)
   {
    memmove(buff+goffset,buff+goffset+gapsize,offset-goffset-gapsize);
    b->goffset+=offset-goffset-gapsize;
   }

}


void closebuffer(buffer * b)
{
 setgapto(b,b->len);
}


/* shift a buffer by the given amount */ 

void shiftbuffer(buffer * b,int shift)
{
 char * to=((char *)b)+shift;
 char * fr=(char *)b;
 if(shift)
 {
  *(b->anchor)=(buffer *)(((char *)b)+shift);
  memmove(to,fr,b->maxlen);
 }
}


/* attempt to push stuff higher until you reach b */

void compactup(buffer * b)
{
 buffer * w=(buffer *)hibuff;
 buffer * last=NULL;
 int      shift;
 int      newmax;

 shift=top-(hibuff+((buffer*)hibuff)->maxlen);

 while(1)
 {
  if(w==b) break;
  if(!w->anchor)
    {
     shift+=w->maxlen;
     w=(buffer*)(((char*)w)-w->previous);
    }
  else
    {
     closebuffer(w);
     newmax=(w->len+3) & 0xFFFFFFFC;
     shift+=w->maxlen-newmax;
     w->maxlen=newmax;
     if(((int)w)==hibuff) hibuff+=shift;
     shiftbuffer(w,shift);
     if(last) last->previous=newmax;
     last=(buffer*)(((int)w)+shift);
     w=(buffer*)(((int)w)-last->previous);
    }
 }

 extendbuffer(b,b->maxlen+shift);
/* dumpstats("compact up");  */
}



/* attempt to push stuff lower until you reach b */

void compactdown(buffer * b)
{
 buffer * w=(buffer *)base;
 int      drop=0;
 int      previous=0;
 int      newmax;
 int      hit;

 while(1)
 {
  if(!w->anchor)
  {
   drop+=w->maxlen;
   w=(buffer*)(((int)w)+w->maxlen);
  }
  else
  {
   hit=(w==b);
   if(((int)w)==hibuff) hibuff-=drop;
   shiftbuffer(w,-drop);
   w=(buffer*)(((int)w)-drop);
   w->previous=previous;
   if(hit) break;
   closebuffer(w);
   newmax=(w->len+3) & 0xFFFFFFFC;
   drop+=w->maxlen-newmax;
   w->maxlen=newmax;
   previous=newmax;
   w=(buffer*)(((int)w)+drop+newmax);
  }
 }

 if(!((int)w==hibuff))
      extendbuffer(w,w->maxlen+drop);

/* dumpstats("compact down");  */
}


/* push everything down as far as it will go    */

void compactdownall(void)
{
 buffer * b;
 compactdown((buffer*)hibuff);
 b=(buffer *)hibuff;
 closebuffer(b);
 b->maxlen=(b->len+3) & 0xFFFFFFFC;
}



/* called from zero poll after memory size has decreased */

void memorydemon(void)
{
 int hi;
 int n;
 int lost;

 remzeroevent(MEMDEMON);

 if(freebuff<MAXFREE) return;
 compactdownall();

 hi=hibuff+((buffer *)hibuff)->maxlen;
 n=(himem-hi)/SLOTSTEP;

 if(n)
 {
  sethimem(himem-n*SLOTSTEP);
  lost=himem-top;
  freebuff+=lost;
  top=himem;
 }
}



/*****************************************************************************/
                    /* allocate and deallocate buffers */


/* actually allocate a buffer at the top of buffer space */

void allocbuff(buffer ** anchor,int location)
{
 buffer * bp=(buffer *)location;
 bp->maxlen=sizeof(buffer)-4;
 bp->len=bp->maxlen;
 bp->goffset=bp->len;
 bp->anchor=anchor;
 freebuff-=bp->maxlen;
 if(hibuff)
     bp->previous=((buffer *)hibuff)->maxlen;
 else
     bp->previous=0;
 hibuff=location;
 *anchor=bp;
}


/* create a null buffer */

void createbuffers(buffer ** anchor)
{
  int hi;

  if(!hibuff)
       {
        allocbuff(anchor,base);
       }
  else
       {
        hi=hibuff+((buffer *)hibuff)->maxlen;
        if(hi<(top-sizeof(buffer)-4))
              allocbuff(anchor,hi);
        else
        if(freebuff>(sizeof(buffer)-4))
              {
               compactdownall();
               hi=hibuff+((buffer *)hibuff)->maxlen;
               allocbuff(anchor,hi);
              }
        else
              {
               sethimem(himem+SLOTSTEP);
               freebuff+=himem-top;
               top=himem;
               if(freebuff>(sizeof(buffer)-4))
                       allocbuff(anchor,hi);
              }
       }
}


/* attempt to change the size of a buffer */

void setbuffsize(buffer ** anchor,int size)
{
 buffer * bp=*anchor;
 int      delta;
 int      new;

 size=(size+3) & 0xFFFFFFFC;     /* quantify in word units */
 delta=size-bp->maxlen;


 if(delta>0)  /* the buffer is getting larger  */
 {
  if(freebuff>delta)
    {
     compactdown(bp);
     bp=*anchor;
     delta=size-bp->maxlen;
     if(delta>0)
         compactup(bp);
    }
  else
    {
     sethimem(himem+SLOTSTEP*(delta/SLOTSTEP+1));
     new=himem-top;
     freebuff+=new;
     top=himem;
     compactup(bp);
     bp=*anchor;
     delta=size-bp->maxlen;
     if(delta>0)
         compactdown(bp);
    }

 }
 else
 if(delta<0)  /* the buffer is getting smaller */
 {
  addzeroevent(MEMDEMON);
 }
}


/* delete a buffer */

void deletebuffers(buffer ** anchor)
{
  /* always try to push the free memory higher */

 buffer * bp=*anchor;

 if(!bp) return;

 if(bp==(buffer *)hibuff)     /* trashing the top buffer */
   {
    hibuff-=bp->previous;
    freebuff+=(bp->len+3) & 0xFFFFFFFC;
    *anchor=NULL;

    while(1)
    {
     bp=(buffer *)hibuff;
     if(!bp->anchor) hibuff-=bp->previous;
     else            break;
    }
   }
 else
   {
    *anchor=NULL;
    freebuff+=(bp->len+3) & 0xFFFFFFFC;
    bp->anchor=NULL;
    /* compactdownall(); */
   }

  addzeroevent(MEMDEMON); 
}




int insertblock(buffer * bp,int offset,int n)
{
 int oldlen,newlen,gapsize;

 buffer ** anchor=bp->anchor;

 if((bp->maxlen-bp->len)<(n+1))
  {
   setbuffsize(anchor,bp->maxlen+n+BUFFSTEP);
  }

 gapsize=(*anchor)->maxlen-(*anchor)->len;
 if(n>gapsize) n=gapsize;

 setgapto(*anchor,offset);
 (*anchor)->goffset+=n;
 oldlen=(*anchor)->len;
 (*anchor)->len+=n;
 newlen=(*anchor)->len;
 freebuff-=((newlen+3) & 0xFFFFFFFC)-((oldlen+3) & 0xFFFFFFFC);
 return(n);
}


int deleteblock(buffer * bp,int offset,int n)
{
 int oldlen,newlen;

 buffer ** anchor=bp->anchor;

 setgapto(*anchor,offset);
 oldlen=(*anchor)->len;
 (*anchor)->len-=n;
 newlen=(*anchor)->len;
 freebuff-=((newlen+3) & 0xFFFFFFFC)-((oldlen+3) & 0xFFFFFFFC);
 addzeroevent(MEMDEMON);
 return(n);
}



/*****************************************************************************/
                      /* manipulate buffer contents */


/* create a buffer using anchor */

void createbuffer(buffer ** anchor)
{
 createbuffers(anchor);
}


/* delete the buffer that anchor points to */

void deletebuffer(buffer ** anchor)
{
 deletebuffers(anchor);
}


/* change length at offset by n */

int alterbuffer(buffer * bp,int offset,int n)
{
 offset+=sizeof(buffer)-4;
 if(n<0) n=-deleteblock(bp,offset,-n);
 else    n=insertblock(bp,offset,n);
 return(n);
}


/* ensures a continuous block of given size */

void formblock(buffer * bp,int offset,int size)
{
 int off;
 if(offset<0) offset=0;
 offset+=sizeof(buffer)-4;
 off=offset+size;
 if(off>bp->len) off=bp->len;
 setgapto(bp,off);
}


/* returns the character at the given offset in the buffer */

char * charbuffer(buffer * bp,int offset)
{
 char   * p;

 p=bp->buff+offset;
 offset+=sizeof(buffer)-4;
 if(offset>=bp->goffset) p+=bp->maxlen-bp->len;

 return(p);
}


/* reads a safe block in the buffer */

void blockbuffer(buffer * bp,int offset,char ** p,int * n)
{
 char   * q=bp->buff+offset;
 offset+=sizeof(buffer)-4;
 if(offset>=bp->goffset) {q+=bp->maxlen-bp->len;*n=bp->len-bp->goffset;}
 else                    {*n=bp->goffset-offset;}
 *p=q;
}



int setsizebuffer(buffer ** bp,int newsize)
{
 int len=SIZEBUFF(*bp);
 int delta;

 delta=newsize-len;

 if(delta>0) alterbuffer(*bp,len,delta);
 else
 if(delta<0) {
              if(delta<-len) delta=-len;
              alterbuffer(*bp,len+delta,delta);
             }

 return(SIZEBUFF(*bp));
}



/* given an anchor, puts the attached stuff in a file */
/* notice buffer is saved with no gap */

int savebuffer(buffer * bp,FILE * fp)
{
 rwrite(bp,1,bp->goffset,fp);
 rwrite((char*)(((int)bp)+bp->goffset+bp->maxlen-bp->len),
                                            bp->len-bp->goffset,1,fp);
 if(rerror(fp)) return(1);
 else           return(0);
}


/* given an anchor, loads and attaches a buffer from file */

int loadbuffer(buffer ** anchor,FILE * fp)
{
 buffer temp;

 int    blen;
 int    rlen;

 createbuffer(anchor);
 rread(&temp,sizeof(buffer)-4,1,fp);
 if(rerror(fp)) {deletebuffer(anchor);return(1);}

 blen=temp.len-sizeof(buffer)+4;

 rlen=setsizebuffer(anchor,blen);

 if(rlen<blen)  {deletebuffer(anchor);return(2);}

 formblock(*anchor,0,blen);
 rread((char *)(((int)*anchor)+sizeof(buffer)-4),blen,1,fp);
 if(rerror(fp)) {deletebuffer(anchor);return(1);}

 return(0);
}


void copybuffer(buffer ** dest,buffer ** src)
{
 int len;

 len=SIZEBUFF(*src);
 formblock(*src,0,len);
 setsizebuffer(dest,len);
 memcpy(charbuffer(*dest,0),charbuffer(*src,0),len);
}



/* given two things that point at a buffer change its pointer from */
/* the old one to the new one */

void relocanchor(buffer ** oldanchor,buffer ** newanchor)
{
 buffer * temp;
 temp=*oldanchor;
 if(temp) temp->anchor=newanchor;
}


/****************************************************************************/
                          /* Node Management   */

#define SIZES 4

#define MAXFREENODE 0x4000
#define MINFREENODE 0x400
#define INITNODE    0x2000
#define NODESTEP    0x2000


buffer * nodebuff;      /* buffer to keep the nodes in */

xstr * lonode[SIZES];   /* points to lowest  free node */
xstr * hinode[SIZES];   /* points to highest free node */
int    tsize[SIZES];

int    nodetop;         /* this points to the end of the highest node */
int    nodeend;         /* this points to the end of node space       */


/* allocate and free nodes */
/* when the difference between nodetop and nodeend is > 16K,tell buffer code */
/* to lower nodeend */
/* when the difference between nodetop and nodeend is < 1K, tell buffer code */
/* to raise nodeend */


void setnodevars(void)
{
 int size;
 size=SIZEBUFF(nodebuff)-1;
 if(size<0) size=0;
 nodeend=(int)charbuffer(nodebuff,size);
}


void increasenodes(void)
{
  int size;
/*
 bbc_vdu(4);bbc_vdu(30);
  printf("increase freebuff=%d maxlen=%d\n",freebuff,nodebuff->maxlen);
  printf("top=%d himem=%d\n",top,himem);
  printf("nodetop=%d nodeend=%d\n",nodetop,nodeend);
  printf("len=%d maxlen=%d goffset=%d\n",nodebuff->len,nodebuff->maxlen,nodebuff->goffset);
 bbc_vdu(5);
 */

 size=SIZEBUFF(nodebuff);
 alterbuffer(nodebuff,size,NODESTEP);
 formblock(nodebuff,0,SIZEBUFF(nodebuff));
 setnodevars();


 /*
 bbc_vdu(4);bbc_vdu(30);
  printf("freebuff=%d maxlen=%d\n",freebuff,nodebuff->maxlen);
  printf("top=%d himem=%d\n",top,himem);
  printf("nodetop=%d nodeend=%d\n",nodetop,nodeend);
  printf("len=%d maxlen=%d goffset=%d\n",nodebuff->len,nodebuff->maxlen,nodebuff->goffset);
 bbc_vdu(5);
  */

}



void reducenodes(void)
{
  int size;

/*
 bbc_vdu(4);bbc_vdu(30);
  printf("dec freebuff=%d maxlen=%d\n",freebuff,nodebuff->maxlen);
  printf("top=%d himem=%d\n",top,himem);
  printf("nodetop=%d nodeend=%d\n",nodetop,nodeend);
 printf("len=%d maxlen=%d goffset=%d\n",nodebuff->len,nodebuff->maxlen,nodebuff->goffset);
 bbc_vdu(5);
 */


 size=SIZEBUFF(nodebuff)-NODESTEP; /* -1;  */
 if(size<INITNODE) return;
 if(size<0) size=0;
 alterbuffer(nodebuff,size,-NODESTEP);
 formblock(nodebuff,0,SIZEBUFF(nodebuff));
 setnodevars();


 /*

 bbc_vdu(4);bbc_vdu(30);
  printf("freebuff=%d maxlen=%d\n",freebuff,nodebuff->maxlen);
  printf("top=%d himem=%d\n",top,himem);
  printf("nodetop=%d nodeend=%d\n",nodetop,nodeend);
  printf("len=%d maxlen=%d goffset=%d\n",nodebuff->len,nodebuff->maxlen,nodebuff->goffset);
 bbc_vdu(5);

  */

}



/* trash node */

void trashnode(xstr * node,int size)
{
 int type=(size>>5)-1;
 int i;
 xstr * p;

 /* if the end of this node==nodetop */
 /* drop nodetop                     */
 /* investigate end of free lists     */

 if(((int)node+size)==nodetop)
 {
  nodetop-=size;

  i=0;
  while(i<SIZES)
    {
     if(((int)hinode[i]+tsize[i])==nodetop)
                 {
                  hinode[i]=hinode[i]->prev;
                  if(hinode[i]) hinode[i]->next=NULL;
                  else
                  lonode[i]=NULL;

                  nodetop-=tsize[i];
                  i=0;
                 }
     else i++;
    }

 }

 /* else */
 /* put this node into appropriate list              */
 /* can merge, insert combined object into next list */

 else
 {

  p=lonode[type];
  if(!p)
  {
   lonode[type]=hinode[type]=node;
   node->next=node->prev=NULL;
  }
  else
  {
   while(p->next && (p<node)) p=p->next;

   if(p<node)
             {
              /* insert node after p  */
              p->next=node;
              node->prev=p;
              node->next=NULL;
              hinode[type]=node;
             }
   else
             {
              /* insert node before p */
              if(p==lonode[type])
                {
                 lonode[type]=node;
                 node->prev=NULL;
                }
              else
                {
                 node->prev=p->prev;
                 (node->prev)->next=node;
                }
              node->next=p;
              p->prev=node;
             }
  }

 }

 /* check for room */

 if((nodeend-nodetop)>MAXFREENODE) reducenodes();
}



/* new node   */

xstr * newnode(int size)
{
 int type=(size>>5)-1;
 int i=type;
 xstr * node;

 /* start from the lo point of the appropriate list */

 while(i<SIZES)
  {
   if(lonode[i])
        {
         node=lonode[i];
         if(node->next)
          { 
           (node->next)->prev=NULL;
           lonode[i]=node->next;
          }
          else
          {
           lonode[i]=hinode[i]=NULL;
          }

         if(i!=type) /* we've just split a node, so add whats left to chain */
            {
             trashnode((xstr *)((int)node+size),tsize[i-type-1]);
            }

         return(node);
        }
   i++;
  }


 /* can't find a space */
 /* allocate at nodetop */

  node=(xstr *)nodetop;
  nodetop+=size;

 /* check for room */

 if((nodeend-nodetop)<MINFREENODE) increasenodes();

 return(node);
}



/* returns the size of an xstr to the nearest 32 bytes */

int xsize(int type)
{
 int size;

 switch(type)
 {
     case XVBOX:
                size=32;
                break;

  case  XSOURCE:
  case XPICTURE:
  case   XFRAME:
  case   XSHELL:
                size=64;
                break;

     case XMAIN:
     case XTEXT:
     case XPAGE:
     case XLINE:
     case XSECT:
        default:
                size=96;
                break;
 }

 return(size);
}


/* destroy an xstr       */

void frexstr(xstr * x)
{
 int size;
 size=xsize(x->type);
 trashnode(x,size);
}


/* create an xstr       */

xstr * newxstr(int type)   /* create a new xstr */
{
  xstr * x;
  int  * i;
  int    j;
  int    size;

  size=xsize(type);
  x=newnode(size);

  j=size;
  for(i=(int *)x;j;i++,j-=4) *i=0;
  x->type=type;
  return(x);
}


/* zero a node without changing its type */

/*

void zerox(xstr * x)
{
 int * i;
 int   j;

 j=xsize(x->type)-sizeof(int);
 for(i=((int *)x)+1;j;i++,j-=sizeof(int)) *i=0;
}

 */


/* init node management */

void nodeinit(void)
{
 int i;

 for(i=0;i<SIZES;i++)
         {
          lonode[i]=hinode[i]=NULL;
          tsize[i]=(i+1)<<5;
         }

 createbuffer(&nodebuff);
 alterbuffer(nodebuff,0,INITNODE);
 formblock(nodebuff,0,SIZEBUFF(nodebuff));
 nodetop=(int)charbuffer(nodebuff,0);
 setnodevars();
}



/*****************************************************************************/
                       /* Overall Memory Control */


/* OS requesting change in slot size to given amount */

void setmem(int size)
{
 /* if(size>=minslotsize) changeslotsize(size);  */
 size=0;

 dccleanup();
 memorydemon();
}



/* boot memory manager */

void meminit(void)
{
 minslotsize=slotsize();
 lomem=minslotsize+BASEPAGE;    /* BASEPAGE is bottom of program */
 himem=lomem;
 sethimem(himem+MINMEM);
 if((himem-lomem)<MINMEM) werr(1,"Not enough memory");
 minslotsize=slotsize();
 buffinit();
 nodeinit();
}
